package tom.spring.cloud.consumer.service;

import com.netflix.hystrix.*;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheKey;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheRemove;
import com.netflix.hystrix.contrib.javanica.cache.annotation.CacheResult;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.ExecutionException;

/**
 * hystrix服务降级/超时熔断/请求缓存
 * @author ZHUFEIFEI
 */
@Slf4j
@Service
public class HelloService {

    @Autowired
    private RestTemplate restTemplate;

    /**
     *
     * 更多属性{@link HystrixCommandProperties}
     * @param name
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public String userDefinedCmd(String name) throws ExecutionException, InterruptedException {
        HelloCommand helloCommand = new HelloCommand(this.restTemplate, name);
        //阻塞获取结果，可以异步使用  helloCommand.toObservable().toCompletable()
        String data = helloCommand.queue().get();
        log.info("request command response from cache : {}", helloCommand.isResponseFromCache());
        return data;
    }

    /**
     * 继承方式
     * 模拟更新清除请求缓存
     * @param name
     */
    public void updateWithManual(String name) {
        //to do...
        HelloCommand.flushCache(name);
    }

    /**
     * 等效于自己实现HystrixCommand类。
     * 注意：CacheResult和CacheRemove注解只有和HystrixCommand一起使用才会生效
     * {@link com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager}
     * @param name
     * @return
     */
    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand(
            fallbackMethod = "defaultValue",
            groupKey = "annotation_hello_group_key",
            commandKey = "annotation_hello_cmd_key", //要和清除缓存的地方配置的一致
            commandProperties = {
                    @HystrixProperty(name="execution.timeout.enabled", value="true"),
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="100")
            }
    )
    @CacheResult(cacheKeyMethod = "annotationCacheKey") //注解使用请求缓存，cacheKeyMethod优先级高于@CacheKey参数注解
    public String annotationMethod(String name){
        ResponseEntity<String> resp = this.restTemplate.exchange("http://provider01/hello/timeout/{1}"
                , HttpMethod.GET, null, String.class, name);
        return resp.getBody();
    }

    String defaultValue(String name, Throwable e) {
        log.warn("service invoke timeout for {}", name);
        log.error("service invoke exception : {}", e.toString());
        return "hello anonymous!";
    }

    /**
     * commandKey要与注解缓存处一致
     * @param name
     */
    @com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand(
            groupKey = "annotation_hello_group_key",
            commandKey = "annotation_hello_cmd_key"
    )
    @CacheRemove(commandKey = "annotation_hello_cmd_key", cacheKeyMethod = "annotationCacheKey")
    public void updateWithAnnotation(String name) {
        //to do..
        log.info("remove cache : annotation_hello_cmd_key => {}", name);
    }

    /**
     * cacheKey 生成方法
     * @param name
     * @return
     */
    public String annotationCacheKey(String name) {
        return name;
    }

}

/**
 * 自定义hystrix熔断处理
 */
@Slf4j
class HelloCommand extends HystrixCommand<String> {

    private RestTemplate restTemplate;
    private String name;
    private static final String REQ_HELLO_KEY = "my_cmd_key";

    protected HelloCommand(RestTemplate restTemplate, String name) {
        super(HystrixCommand.Setter.withGroupKey(
                //自定义分组key
                HystrixCommandGroupKey.Factory.asKey("my_group_key"))
                //自定义命令key
                .andCommandKey(HystrixCommandKey.Factory.asKey(REQ_HELLO_KEY))
                //其他属性配置,启用执行超时100ms
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionTimeoutEnabled(true)
                        .withExecutionTimeoutInMilliseconds(100)
                ));
        this.restTemplate = restTemplate;
        this.name = name;
    }

    /**
     * 自定义实现
     * @return
     * @throws Exception
     */
    @Override
    protected String run() throws Exception {
        ResponseEntity<String> resp = this.restTemplate.exchange("http://provider01/hello/timeout/{1}"
                , HttpMethod.GET, null, String.class, this.name);
        return resp.getBody();
    }

    /**
     * callback 熔断时，返回的信息
     * @return
     */
    @Override
    protected String getFallback() {
        return "hello anonymous";
    }

    @Override
    protected String getCacheKey() {
        //使用请求缓存，重写该方法
        return this.name;
    }

    public static void flushCache(String name) {
        log.info("remove cache : {} => {}", REQ_HELLO_KEY, name);
        //清除缓存key对应的请求缓存
        HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey(REQ_HELLO_KEY)
                , HystrixConcurrencyStrategyDefault.getInstance())
                .clear(name);
    }
}